﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Kawanon.Usability;
using QTPlugin;
using Microsoft.Win32;
using Kawanon.Usability.Actions;
using Kawanon.Localization;

namespace Kawanon.QtHoney
{
	public partial class ExpressionsView : UserControl
	{
		private const int D_NORTH = 0;
		private const int D_EAST = 1;
		private const int D_SOUTH = 2;
		private const int D_WEST = 3;

		private IList<GestureExpression> m_defaults = GestureSettings.GetDefaultExpressions();
		private Dictionary<Action, GestureExpression> m_exprs = new Dictionary<Action, GestureExpression>();
		private Dictionary<string, ListViewGroup> m_groups = new Dictionary<string, ListViewGroup>();
		private IPluginServer m_server;
		private int m_nCustomActionInsertion = 0;
		private ScrollEffect m_effect;
		private ScrollEffect m_effectXButtons;

		[ResourceKey("ExpressionsView_Text")]
		private enum Resources
		{
			Control0,
			Control1,
			Control2,
			Control3,
			BalloonTitle,
			BalloonText,
		}

		private class TrailsAlreadyExistsException : ApplicationException
		{
			public GestureExpression Conflict { get; private set; }

			public TrailsAlreadyExistsException(GestureExpression expr)
			{
				Conflict = expr;
			}
		}

		public IPluginServer Server
		{
			get
			{
				return m_server;
			}
			set
			{
				m_server = value;

				if (m_server != null)
				{
					contextMenuStrip1.Renderer = m_server.GetMenuRenderer();
				}
			}
		}

		public ListView ListView
		{
			get
			{
				return listView1;
			}
		}

		public ExpressionsView()
		{
			InitializeComponent();

			m_effect = new ScrollEffect(panelLabelEdit);
			m_effectXButtons = new ScrollEffect(panelXButtons);
			m_effectXButtons.Interval *= 5;
		}

		private void CheckConflict(GestureExpression expr)
		{
			if (!expr.TrailsEquals(GestureExpression.EmptyTrails))
			{
				IList<GestureExpression> list = GestureSettings.OverrideExpressions(m_defaults, m_exprs.Values);
				GestureExpression conflict = GestureSettings.FindExpression(list, expr.Trails, expr.Button);
				if (conflict != null && !conflict.Action.Equals(expr.Action))
				{
					throw new TrailsAlreadyExistsException(conflict);
				}
			}
		}

		private void ChangeExpression(ListViewItem item, GestureExpression expr, bool userEdit)
		{
			if (userEdit)
			{
				CheckConflict(expr);
			}
			GestureExpression defaultExpr = GestureSettings.FindExpression(m_defaults, expr.Action);
			GestureExpression oldExpr = null;
			m_exprs.TryGetValue(expr.Action, out oldExpr);
			string urdl = expr.FormatTrails();

			if (defaultExpr == null)
			{
				item.ForeColor = Color.Black;

				m_exprs.Remove(expr.Action);
				m_exprs.Add(expr.Action, expr);
			}
			else if (!defaultExpr.TrailsEquals(expr) || defaultExpr.Button != expr.Button)
			{
				item.ForeColor = Color.Blue;
				//item.Checked = true;	// ここで与える値はなんでも良い

				m_exprs.Remove(expr.Action);
				m_exprs.Add(expr.Action, expr);
			}
			else
			{
				item.ForeColor = (urdl == String.Empty) ? Color.Gray : Color.Black;
				//item.Checked = false;

				m_exprs.Remove(expr.Action);
			}
			item.Text = urdl;
			item.Tag = expr;
			item.SubItems[1].Text = expr.Name;
			item.SubItems[3].Text = GestureExpression.GetButtonSymbol(
				(TriggerButtons)Utility.KeyToMouseButton(expr.Button));

			NotifyItemChanged(expr);
		}

		private ListViewItem AddItem(GestureExpression expr)
		{
			ListViewItem item = new ListViewItem(new string[] {
				String.Empty, String.Empty,
				(GestureSettings.IsAcceptTarget(expr.Action) ? "OK" : String.Empty),
				String.Empty });
			item.Tag = expr;
			string category = GestureSettings.GetCategory(expr.Action);
			ListViewGroup group;
			if (!m_groups.TryGetValue(category, out group))
			{
				group = new ListViewGroup(category);
				if (expr.Action is CustomAction)
				{
					listView1.Groups.Insert(0, group);
				}
				else
				{
					listView1.Groups.Add(group);
				}
				m_groups.Add(category, group);
			}
			item.Group = group;
			if (expr.Action is CustomAction)
			{
				listView1.Items.Insert(m_nCustomActionInsertion, item);
				m_nCustomActionInsertion++;
			}
			else
			{
				listView1.Items.Add(item);
			}
			ChangeExpression(item, expr, false);
			return item;
		}

		private bool IsXButtonsPrevented()
		{
			if (m_server == null)
			{
				return true;
			}
			bool enabled = false;
			if (!m_server.TabBarOption.TryGetValue(TabBar_Options_Boolean.XButtons, out enabled))
			{
				return false;
			}
			return enabled;
		}

		private void NotifyItemChanged(GestureExpression expr)
		{
			if (panelXButtons.Visible)
			{
				return;
			}

			if (expr.Button != Keys.XButton1 && expr.Button != Keys.XButton2)
			{
				return;
			}
			if (expr.Trails.Length == 0)
			{
				return;
			}

			if (!IsXButtonsPrevented())
			{
				return;
			}
			Utility.AttachImage(pictureBoxXButtons, SystemIcons.Exclamation);
			m_effectXButtons.SetVisible(true);
		}

		private void UpdateItems(IEnumerable<GestureExpression> list)
		{
			m_nCustomActionInsertion = 0;

			listView1.Items.Clear();
			listView1.SuspendLayout();

			foreach (GestureExpression expr in list)
			{
				AddItem(expr);
			}
			listView1.ResumeLayout();
		}

		private ListViewItem FindItem(object tag)
		{
			foreach (ListViewItem item in listView1.Items)
			{
				if (item.Tag == tag)
				{
					return item;
				}
			}
			return null;
		}

		private void ResolveConflict(GestureExpression oldExpr, GestureExpression newExpr, GestureExpression issueExpr)
		{
			List<GestureExpression> src = new List<GestureExpression>(m_exprs.Values);
			src.Remove(issueExpr);
			src.Add(new GestureExpression(GestureExpression.EmptyTrails, issueExpr));
			src.Remove(oldExpr);
			src.Add(newExpr);
			IList<GestureExpression> list = GestureSettings.OverrideExpressions(m_defaults, src);
			UpdateItems(list);

			ListViewItem newItem = FindItem(newExpr);
			newItem.Focused = true;
			newItem.Selected = true;
			newItem.EnsureVisible();
		}

		delegate GestureExpression EditExpressionDelegate(GestureExpression oldExpr);

		private void EditExpression(EditExpressionDelegate func)
		{
			ListViewItem item = listView1.FocusedItem;
			if (item == null)
			{
				return;
			}
			GestureExpression oldExpr = (GestureExpression)item.Tag;
			GestureExpression newExpr = func.Invoke(oldExpr);
			if (newExpr == null)
			{
				return;
			}
			try
			{
				ChangeExpression(item, newExpr, true);
			}
			catch (TrailsAlreadyExistsException ex)
			{
				string text = String.Format(
					StringResource.Localize(MiscResources.TrailsAlreadyExists),
					newExpr.FormatTrails(), ex.Conflict.Name, newExpr.Name);
				DialogResult res = Utility.DoModal(m_server, delegate()
				{
					return MessageBox.Show(this, text,
						StringResource.Localize(Caption.Confirm, false),
						MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
				});
				if (res == DialogResult.Yes)
				{
					ResolveConflict(oldExpr, newExpr, ex.Conflict);
				}
			}

			toolStripButton_Rollback.Enabled = true;
		}

		private void EditDialog()
		{
			EditExpression(delegate(GestureExpression oldExpr)
			{
				GestureEditForm form = new GestureEditForm();
				form.Expression = oldExpr;

				if (Utility.DoModal(m_server, form) != DialogResult.OK)
				{
					return null;
				}

				GestureExpression expr = new GestureExpression(oldExpr.Action, form.Expression);
				return expr;
			});
		}

		private void DeleteItem(ListViewItem item)
		{
			GestureExpression expr = (GestureExpression)item.Tag;
			if (!(expr.Action is CustomAction))
			{
				Dbg.Abort("expr.Action is not CustomAction");
			}
			m_exprs.Remove(expr.Action);
			listView1.Items.Remove(item);

			toolStripButton_Rollback.Enabled = true;
		}

		private void AddCustomAction(CustomAction action)
		{
			DialogResult res = action.ShowEditDialog(this.Handle);
			if (res == DialogResult.Cancel)
			{
				return;
			}

			GestureExpression expr = new GestureExpression(GestureExpression.EmptyTrails, action);
			ListViewItem item = AddItem(expr);
			item.Focused = true;
			item.Selected = true;
			item.EnsureVisible();
			toolStripButton_Rollback.Enabled = true;
		}

		public void Save()
		{
			if (!IsHandleCreated)
			{
				// コントロールが表示されてないのに保存すると設定が消えてしまう
				//MessageBox.Show("Cancel");
				return;
			}

			GestureSettings.SaveExpressions(m_exprs.Values);

			RegistryKey key = Plugins.OpenClassRegistryKey(GetType(), true);
			key.SetValue("ShowGroups", toolStripButton_ShowGroups.Checked, RegistryValueKind.DWord);
			key.Flush();
			key.Close();
		}

		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
		{
			if (listView1.FocusedItem != null)
			{
				switch (keyData)
				{
					case Keys.F2:
						listView1.FocusedItem.BeginEdit();
						return true;

					case Keys.Delete:
						if (((GestureExpression)listView1.FocusedItem.Tag).Action is CustomAction)
						{
							DeleteItem(listView1.FocusedItem);
						}
						else
						{
							EditExpression(delegate(GestureExpression expr)
							{
								return new GestureExpression(GestureExpression.EmptyTrails, expr);
							});
						}
						return true;
				}
			}
			return base.ProcessCmdKey(ref msg, keyData);
		}

		private void ExpressionsView_Load(object sender, EventArgs e)
		{
			StringResource.Apply("ExpressionsView_Text",
				label1, labelXButtons, linkLabelWhy, linkLabelActivateXButtons);
			StringResource.Apply("ExpressionsView_ToolBar", toolStrip1.Items);
			StringResource.Apply("ExpressionsView_ListView_Columns", listView1.Columns);
			StringResource.Apply("ExpressionsView_ListView_ContextMenu", contextMenuStrip1.Items);

			Utility.EnableDoubleBuffer(listView1);

			IList<GestureExpression> list = GestureSettings.LoadAllExpressions();
			UpdateItems(list);

			RegistryKey key = Plugins.OpenClassRegistryKey(GetType(), false);
			if (key != null)
			{
				object obj = key.GetValue("ShowGroups", toolStripButton_ShowGroups.Checked);
				toolStripButton_ShowGroups.Checked = Convert.ToBoolean(obj);
				key.Close();
			}
		}

		private void listView1_BeforeLabelEdit(object sender, LabelEditEventArgs e)
		{
			m_effect.SetVisible(true);

			Utility.AttachImage(pictureBoxLabelEdit, SystemIcons.Information);
		}

		private void listView1_AfterLabelEdit(object sender, LabelEditEventArgs e)
		{
			m_effect.SetVisible(false);

			if (e.Label == null)
			{
				return;
			}
			ListViewItem item = listView1.Items[e.Item];
			GestureExpression expr = (GestureExpression)item.Tag;

			//string str = e.Label.ToUpper();
			//str.Replace('↑', 'U');
			//str.Replace('→', 'R');
			//str.Replace('↓', 'D');
			//str.Replace('←', 'L');
			try
			{
				EditExpression(delegate(GestureExpression oldExpr)
				{
					return new GestureExpression(e.Label, expr);
				});
				e.CancelEdit = true;
			}
			catch (FormatException)
			{
				e.CancelEdit = true;
				//TODO: バルーンを出す
				System.Media.SystemSounds.Hand.Play();
			}
		}

		private void listView1_ItemCheck(object sender, ItemCheckEventArgs e)
		{
			//ListViewHitTestInfo hi = listView1.HitTest(listView1.PointToClient(Cursor.Position));
			//if (hi.Item != null && hi.Location != ListViewHitTestLocations.StateImage)
			//{
			//    // ダブルクリックではチェック状態を変化させない
			//    e.NewValue = hi.Item.Checked ? CheckState.Checked : CheckState.Unchecked;
			//}

			//常にチェックを許可しない
			ListViewItem item = listView1.Items[e.Index];
			e.NewValue = (item.ForeColor == Color.Blue) ? CheckState.Checked : CheckState.Unchecked;
		}

		private void listView1_ItemActivate(object sender, EventArgs e)
		{
			EditDialog();
		}

		private void ToolStripMenuItem_EditExpression_Click(object sender, EventArgs e)
		{
			EditDialog();
		}

		private void EditAction()
		{
			EditExpression(delegate(GestureExpression expr)
			{
				DialogResult res = ((CustomAction)expr.Action).ShowEditDialog(this.Handle);
				if (res == DialogResult.Cancel)
				{
					return null;
				}
				return expr;
			});
		}

		private void ToolStripMenuItem_EditAction_Click(object sender, EventArgs e)
		{
			EditAction();
		}

		private void ToolStripMenuItem_Choose_Click(object sender, EventArgs e)
		{
			EditExpression(delegate(GestureExpression oldExpr)
			{
				List<GestureExpression> exprs = GestureSettings.OverrideExpressions(m_defaults, m_exprs.Values);
				exprs = exprs.FindAll(delegate(GestureExpression expr)
				{
					return expr.Button == oldExpr.Button;
				});

				int[][] raws = GestureSettings.EnumerateFreeTrails(exprs, 5);

				string[] titles = Array.ConvertAll(raws,
					(t) => GestureExpression.FormatTrails(t));

				ChooseFromArrayForm form = new ChooseFromArrayForm();
				form.Source = titles;
				DialogResult res = form.ShowDialog(this);
				if (res == DialogResult.Cancel)
				{
					return null;
				}

				string title = (string)form.Selected;
				return new GestureExpression(title.Split(' ')[0], oldExpr);
			});
		}

		private void ToolStripMenuItem_Reset_Click(object sender, EventArgs e)
		{
			EditExpression(delegate(GestureExpression oldExpr)
			{
				GestureExpression defaultExpr = GestureSettings.FindExpression(m_defaults, oldExpr.Action);
				if (defaultExpr == null)
				{
					Dbg.Abort("defaultExpr == null");
				}
				GestureExpression expr = new GestureExpression(oldExpr.Action, defaultExpr);
				return expr;
			});
		}

		private void ToolStripMenuItem_Clear_Click(object sender, EventArgs e)
		{
			EditExpression(delegate(GestureExpression expr)
			{
				return new GestureExpression(GestureExpression.EmptyTrails, expr);
			});
		}

		private void ToolStripMenuItem_Delete_Click(object sender, EventArgs e)
		{
			ListViewItem item = listView1.FocusedItem;
			if (item == null)
			{
				return;
			}
			DeleteItem(item);
		}

		private void ToolStripMenuItem_EditLabel_Click(object sender, EventArgs e)
		{
			ListViewItem item = listView1.FocusedItem;
			if (item == null)
			{
				return;
			}
			item.BeginEdit();
		}

		private void toolStripButton_Rollback_Click(object sender, EventArgs e)
		{
			DialogResult res = MessageBox.Show(
				StringResource.Localize(MiscResources.ConfirmRevert),
				StringResource.Localize(Caption.Confirm, false),
				MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
			if (res == DialogResult.Cancel)
			{
				return;
			}

			m_exprs.Clear();

			List<GestureExpression> src = new List<GestureExpression>();
			GestureSettings.LoadExpressions(src);

			IList<GestureExpression> list = GestureSettings.OverrideExpressions(m_defaults, src);
			UpdateItems(list);

			toolStripButton_Rollback.Enabled = false;
		}

		private void toolStripButton_ShowGroups_CheckStateChanged(object sender, EventArgs e)
		{
			listView1.ShowGroups = toolStripButton_ShowGroups.Checked;
		}

		private void ToolStripMenuItem_CreateProcess_Click(object sender, EventArgs e)
		{
			AddCustomAction(new CreateProcess());
		}

		private void ToolStripMenuItem_OpenFolder_Click(object sender, EventArgs e)
		{
			AddCustomAction(new OpenFolder());
		}

		private void ToolStripMenuItem_CallPlugin_Click(object sender, EventArgs e)
		{
			AddCustomAction(new CallPlugin());
		}

		private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
		{
			ListViewItem item = listView1.FocusedItem;
			if (item == null)
			{
				return;
			}
			GestureExpression expr = (GestureExpression)item.Tag;
			GestureExpression defaultExpr = GestureSettings.FindExpression(m_defaults, expr.Action);
			if (defaultExpr == null)
			{
				ToolStripMenuItem_Reset.Visible = false;
				ToolStripMenuItem_Clear.Visible = false;
				ToolStripMenuItem_Delete.Visible = true;
				ToolStripMenuItem_EditAction.Visible = true;
			}
			else
			{
				ToolStripMenuItem_Reset.Visible = true;
				ToolStripMenuItem_Clear.Visible = true;
				ToolStripMenuItem_Delete.Visible = false;
				ToolStripMenuItem_EditAction.Visible = false;
			}
		}

		private void listView1_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (listView1.SelectedItems.Count < 1)
			{
				toolStripButton_EditAction.Enabled = false;
				return;
			}
			ListViewItem item = listView1.SelectedItems[0];
			GestureExpression expr = (GestureExpression)item.Tag;

			bool editable = expr.Action is CustomAction;
			toolStripButton_EditAction.Enabled = editable;
		}

		private void toolStripButton_EditAction_Click(object sender, EventArgs e)
		{
			EditAction();
		}

		private void linkLabelWhy_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			ToolTip tip = (ToolTip)linkLabelWhy.Tag;
			// なぜか何度もクリックすると出なくなるので
			tip.RemoveAll();
			tip.SetToolTip(linkLabelWhy,
				StringResource.Localize(Resources.BalloonText));
			//	tip.Active = true;
		}

		private void linkLabelWhy_Enter(object sender, EventArgs e)
		{
			ToolTip tip = Utility.Tag<ToolTip>(linkLabelWhy, () =>
			{
				tip = Utility.CreateBalloonTip(ToolTipIcon.Info, StringResource.Localize(Resources.BalloonTitle));
				tip.InitialDelay = 100;
				tip.AutoPopDelay *= 10;
				//tip.AutomaticDelay = 100;
				//tip.ShowAlways = true;

				tip.SetToolTip(linkLabelWhy, StringResource.Localize(Resources.BalloonText));
				return tip;
			});
		}

		private void linkLabelActivateXButtons_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			if (m_server != null)
			{
				TabBarOption options = m_server.TabBarOption;
				options.SetValue(TabBar_Options_Boolean.XButtons, false);
				m_server.TabBarOption = options;
			}
			m_effectXButtons.SetVisible(false);
		}
	}
}
